-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local bs = require 'mc.bitstring'
local json = require 'cjson'
local context = require 'mc.context'

local m = {}

local ACTIVATION_DECREASING = 'Decreasing'
local ACTIVATION_INCREASING = 'Increasing'
local HEALTH_STATUS_NUM = {
    [0] = "OK",
    [1] = "Warning",
    [2] = "Warning",
    [3] = "Critical",
}
local HEALTH_STATUS_STR = {
    ['OK'] = 'OK',
    ['Minor'] = 'Warning',
    ['Major'] = 'Warning',
    ['Critical'] = 'Critical'
}

local READING_TYPE = {
    ["degrees C"] = "Temperature",
    ["Volts"] = "Voltage",
    ["Amps"] = "Current",
    ["Watts"] = "EnergyWh",
    ["RPM"] = "Rotational"
}

m.capability = bs.new([[<<
    event_msg_support:2,
    threshold_access_support:2,
    hysteresis_support:2,
    auto_rearm_support:1,
    ignore_support:1
>>]])

m.reading_mask = bs.new([[<<
    readable_lower_noncritical:1,
    readable_lower_critical:1,
    readable_lower_nonrecoverable:1,
    readable_upper_noncritical:1,
    readable_upper_critical:1,
    readable_upper_nonrecoverable:1,
    _:2,
    settable_lower_noncritical:1,
    settable_lower_critical:1,
    settable_lower_nonrecoverable:1,
    settable_upper_noncritical:1,
    settable_upper_critical:1,
    settable_upper_nonrecoverable:1,
    _:2
>>]])

m.assert_mask = bs.new([[<<
    assert_noncritical_lgl:1,
    assert_noncritical_lgh:1,
    assert_critical_lgl:1,
    assert_critical_lgh:1,
    assert_nonrecoverable_lgl:1,
    assert_nonrecoverable_lgh:1,
    assert_noncritical_ugl:1,
    assert_noncritical_ugh:1,
    assert_critical_ugl:1,
    assert_critical_ugh:1,
    assert_nonrecoverable_ugl:1,
    assert_nonrecoverable_ugh:1,
    reading_lower_noncritical:1,
    reading_lower_critical:1,
    reading_lower_nonrecoverable:1,
    _:1
>>]])

function m.get_sensor_threshold(capabilities, mask, data)
    local accessible = true
    local cap = m.capability:unpack(string.pack('I1', capabilities))
    if not cap then
        accessible = false
    elseif cap.threshold_access_support ~= 1 and cap.threshold_access_support ~= 2 then
        accessible = false
    end
    local rm = m.reading_mask:unpack(string.pack('I2', mask))
    if not rm then
        accessible = false
    end

    local threshold = {}
    threshold.UpperThresholdNonCritical = (accessible and rm.readable_upper_noncritical ~= 0) and
        tonumber(data.UpperThresholdNonCritical) or null
    threshold.UpperThresholdCritical = (accessible and rm.readable_upper_critical ~= 0) and
        tonumber(data.UpperThresholdCritical) or null
    threshold.UpperThresholdFatal = (accessible and rm.readable_upper_nonrecoverable ~= 0) and
        tonumber(data.UpperThresholdFatal) or null
    threshold.LowerThresholdNonCritical = (accessible and rm.readable_lower_noncritical ~= 0) and
        tonumber(data.LowerThresholdNonCritical) or null
    threshold.LowerThresholdCritical = (accessible and rm.readable_lower_critical ~= 0) and
        tonumber(data.LowerThresholdCritical) or null
    threshold.LowerThresholdFatal = (accessible and rm.readable_lower_nonrecoverable ~= 0) and
        tonumber(data.LowerThresholdFatal) or null
    return threshold
end

local function get_threshold(value)
    return value ~= 'null' and tonumber(value) or json.null
end

function m.get_threshold_sensors(list, host_type)
    local is_multihost = host_type == 'Multihost'
    local sensors = json.json_object_new_array()

    local sensor
    for i, item in ipairs(list) do
        sensor = json.json_object_new_object()
        sensor.Name = item.SensorName
        sensor.SensorId = item.SensorNumber
        sensor.SystemId = is_multihost and (item.SystemId ~= 0 and tostring(item.SystemId) or json.null) or '1'
        sensor.Status = item.SensorStatus
        sensor.Unit = item.SensorUnit
        sensor.ReadingValue = item.SensorReading ~= 'na' and tonumber(item.SensorReading) or json.null
        sensor.UpperThresholdNonCritical = get_threshold(item.UpperThresholdNonCritical)
        sensor.UpperThresholdCritical = get_threshold(item.UpperThresholdCritical)
        sensor.UpperThresholdFatal = get_threshold(item.UpperThresholdFatal)
        sensor.LowerThresholdNonCritical = get_threshold(item.LowerThresholdNonCritical)
        sensor.LowerThresholdCritical = get_threshold(item.LowerThresholdCritical)
        sensor.LowerThresholdFatal = get_threshold(item.LowerThresholdFatal)
        sensors[i] = sensor
    end

    return sensors
end

function m.get_discrete_sensors(list, host_type)
    local is_multihost = host_type == 'Multihost'
    local sensors = json.json_object_new_array()

    local sensor
    for i, item in ipairs(list) do
        sensor = json.json_object_new_object()
        sensor.Name = item.SensorName
        sensor.SensorId = item.SensorNumber
        sensor.SystemId = is_multihost and (item.SystemId ~= 0 and tostring(item.SystemId) or json.null) or '1'
        sensor.Status = item.SensorStatus ~= 'na' and item.SensorStatus or json.null
        sensors[i] = sensor
    end

    return sensors
end

function m.get_sensors(list, skip, top)
    local sensors = {}
    skip = skip or 0
    top = top or 32

    local list_len = #list
    local sensor_info

    for i = skip + 1, skip + top, 1 do
        if i <= list_len then
            sensor_info = list[i]
            sensors[#sensors + 1] = string.format(string.gsub(sensor_info.SensorName, " ", "") ..
                '.' .. sensor_info.SensorNumber)
        end
    end

    return sensors
end

local function find_last_delimiter(str, delimiter)
    local result = {}
    local i = 0
    while true do
        i = string.find(str, delimiter, i + 1)
        if i == nil then
            break
        end
        result[#result+1] = i
    end
    return result[#result]
end

local owner_luns = {[0] = 0, [1] = 1, [2] = 3}
function m.is_valid_sensor_id(sensor_id, path1, path2)
    local index = find_last_delimiter(sensor_id, '%.')
    if not index then
        -- 没有点号分隔，格式非法
        error(custom_messages.ResourceNotFound('sensor_id', sensor_id))
    end
    local sensor_name = string.sub(sensor_id, 1, index - 1)
    local sensor_number = string.sub(sensor_id,  index + 1)
    local number = tonumber(sensor_number)
    if not number then
        -- sensor number为非数字，转换失败
        error(custom_messages.ResourceNotFound('sensor_id', sensor_id))
    end
    -- Uri校验必须与上级资源的@odata.id完全一致
    -- sensor number为数字字符串但是转换后与原来不一致，如001和1
    if sensor_number ~= tostring(number) then
        error(custom_messages.ResourceNotFound('sensor_id', sensor_id))
    end
    if path1 == "" and path2 == "" then
        error(custom_messages.ResourceNotFound('sensor_id', sensor_id))
    end
    -- SensorName校验
    local obj
    if path1 ~= "" then
        obj = mdb.get_object(bus, path1, "bmc.kepler.Systems.ThresholdSensor")
    else
        obj = mdb.get_object(bus, path2, "bmc.kepler.Systems.DiscreteSensor")
    end
    -- Uri校验,大小写不敏感
    local name_lower = string.lower(string.gsub(obj.SensorName, " ", ""))
    if  name_lower ~= string.lower(sensor_name) then
        error(custom_messages.ResourceNotFound('sensor_id', sensor_id))
    end

    return true
end

function m.get_sensor_lun_number(sensor_id)
    local owner_lun
    local sensor_number
    local index = find_last_delimiter(sensor_id, '%.')
    if not index then
        -- 参数不合法，没有点号分隔，返回0，0
        return {0, 0}
    end
    local number_str = string.sub(sensor_id,  index + 1)
    local number = tonumber(number_str)
    if not number then
        -- 参数不合法，不是数字类型，返回0，0
        return {0, 0}
    end
    if number % 0xFF == 0 then
        owner_lun = owner_luns[number // 0xFF - 1]
        sensor_number = 255
    else
        owner_lun = owner_luns[number // 0xFF]
        sensor_number = number % 0xFF
    end
    return {owner_lun, sensor_number}
end

local function threshold_display(value, activation)
    local threshold_value = {}
    if value == null then
        return null
    else
        threshold_value.Activation = activation
        threshold_value.Reading = value
        return threshold_value
    end
end

local function get_status(sensor_obj, sensor_obj_display)
    local health = null
    local state = 'Disabled'
    local reading = null
    if sensor_obj_display.Status == 'Enabled' then
        if sensor_obj.ReadingStatus ~= 0 then
            health = 'Warning'
            state = 'UnavailableOffline'
        else
            health = HEALTH_STATUS_STR[sensor_obj_display.Health]
            state = 'Enabled'
            reading = sensor_obj_display.ReadingDisplay
        end
    end
    return health, state, reading
end

local function get_sensor_threshold_msg(capabilities, mask, data)
    local accessible = true
    local cap = m.capability:unpack(string.pack('I1', capabilities))
    if not cap then
        accessible = false
    elseif cap.threshold_access_support ~= 1 and cap.threshold_access_support ~= 2 then
        accessible = false
    end
    local rm = m.reading_mask:unpack(string.pack('I2', mask))
    if not rm then
        accessible = false
    end

    local threshold = {}
    threshold.UpperNoncritical = (accessible and rm.readable_upper_noncritical ~= 0) and
        tonumber(data.UpperNoncriticalDisplay) or null
    threshold.UpperCritical = (accessible and rm.readable_upper_critical ~= 0) and
        tonumber(data.UpperCriticalDisplay) or null
    threshold.UpperNonrecoverable = (accessible and rm.readable_upper_nonrecoverable ~= 0) and
        tonumber(data.UpperNonrecoverableDisplay) or null
    threshold.LowerNoncritical = (accessible and rm.readable_lower_noncritical ~= 0) and
        tonumber(data.LowerNoncriticalDisplay) or null
    threshold.LowerCritical = (accessible and rm.readable_lower_critical ~= 0) and
        tonumber(data.LowerCriticalDisplay) or null
    threshold.LowerNonrecoverable = (accessible and rm.readable_lower_nonrecoverable ~= 0) and
        tonumber(data.LowerNonrecoverableDisplay) or null

    local as = m.reading_mask:unpack(string.pack('I2', data.AssertStatus))
    threshold.LowerNoncriticalActivation = as.assert_noncritical_lgl == 0 and
        ACTIVATION_INCREASING or ACTIVATION_DECREASING
    threshold.LowerCriticalActivation = as.assert_critical_lgl == 0 and
        ACTIVATION_INCREASING or ACTIVATION_DECREASING
    threshold.LowerNonrecoverableActivation = as.assert_nonrecoverable_lgl == 0 and
        ACTIVATION_INCREASING or ACTIVATION_DECREASING
    threshold.UpperNoncriticalActivation = as.assert_noncritical_ugl == 1 and
        ACTIVATION_INCREASING or ACTIVATION_DECREASING
    threshold.UpperCriticalActivation = as.assert_critical_ugl == 1 and
        ACTIVATION_INCREASING or ACTIVATION_DECREASING
    threshold.UpperNonrecoverableActivation = as.assert_nonrecoverable_ugl == 1 and
        ACTIVATION_INCREASING or ACTIVATION_DECREASING
    return threshold
end

local function get_threshold_sensor_rsp(sensor_obj, sensor_obj_display, sensor_obj_entity)
    -- 封装status
    local health, state, reading = get_status(sensor_obj, sensor_obj_display)
    local  res = {}

    res.SensorName = sensor_obj.SensorName
    res.EntityName = sensor_obj_entity and sensor_obj_entity.Name or null
    res.Reading = reading ~= null and tonumber(reading) or null
    res.ReadingType = READING_TYPE[sensor_obj_display.UnitDisplay] or null
    res.Unit = sensor_obj_display.UnitDisplay

    res.Health = health
    res.EntityHealth = sensor_obj_entity and HEALTH_STATUS_NUM[sensor_obj_entity.Health] or null
    res.State = state
    res.SensorId = string.format(string.gsub(sensor_obj.SensorName, " ", "") ..
    '.' .. sensor_obj.SensorNumber) 

    local threshold = get_sensor_threshold_msg(sensor_obj.Capabilities, sensor_obj.ReadingMask, sensor_obj_display)
    if threshold.LowerNoncritical == null and threshold.LowerCritical == null and
        threshold.LowerNonrecoverable == null and threshold.UpperNoncritical == null and
        threshold.UpperCritical == null and threshold.UpperNonrecoverable == null then
        res.LowerNoncritical = lua_nil
        res.LowerCritical = lua_nil
        res.LowerNonrecoverable = lua_nil
        res.UpperNoncritical = lua_nil
        res.UpperCritical = lua_nil
        res.UpperNonrecoverable = lua_nil
    else
        res.LowerNoncritical = threshold_display(threshold.LowerNoncritical,
            threshold.LowerNoncritical == null and "Disabled" or threshold.LowerNoncriticalActivation)
        res.LowerCritical = threshold_display(threshold.LowerCritical,
            threshold.LowerCritical == null and "Disabled" or threshold.LowerNonrecoverableActivation)
        res.LowerNonrecoverable = threshold_display(threshold.LowerNonrecoverable,
            threshold.LowerNonrecoverable == null and "Disabled" or threshold.LowerThresholdFatallActivation)
        res.UpperNoncritical = threshold_display(threshold.UpperNoncritical,
            threshold.UpperNoncritical == null and "Disabled" or threshold.UpperNoncriticalActivation)
        res.UpperCritical = threshold_display(threshold.UpperCritical,
            threshold.UpperCritical == null and "Disabled" or threshold.UpperCriticalActivation)
        res.UpperNonrecoverable = threshold_display(threshold.UpperNonrecoverable,
            threshold.UpperNonrecoverable == null and "Disabled" or threshold.UpperNonrecoverableActivation)
    end
    return res
end

local function get_discrete_sensor_rsp(sensor_obj, sensor_obj_display, sensor_obj_entity)
    local res = {}
    -- 封装status
    res.SensorName =  sensor_obj.SensorName
    res.EntityName = sensor_obj_entity and sensor_obj_entity.Name or null
    res.Reading = null
    res.ReadingType = null
    res.Unit = null

    res.Health = HEALTH_STATUS_STR[sensor_obj_display.Health]
    res.EntityHealth = sensor_obj_entity and HEALTH_STATUS_NUM[sensor_obj_entity.Health] or null
    res.State = sensor_obj_display.Status
    res.SensorId = string.format(string.gsub(sensor_obj.SensorName, " ", "") ..
    '.' .. sensor_obj.SensorNumber) 

    res.LowerNoncritical = lua_nil
    res.LowerCritical = lua_nil
    res.LowerNonrecoverable = lua_nil
    res.UpperNoncritical = lua_nil
    res.UpperCritical = lua_nil
    res.UpperNonrecoverable = lua_nil

    return res
end

function m.get_sensor_rsp(threshold_path, discrete_path)
    if threshold_path ~= "" then
        local obj = mdb.get_object(bus, threshold_path, "bmc.kepler.Systems.ThresholdSensor")
        local obj_display = mdb.get_object(bus, threshold_path, "bmc.kepler.Systems.ThresholdSensorDisplay")
        -- 根据EntityId和Instance查找对应的Entity
        local entity_path = bus:call('bmc.kepler.maca', '/bmc/kepler/MdbService', 'bmc.kepler.Mdb', 'GetPath',
            'a{ss}ssb', context.new(), "bmc.kepler.Systems.Entity",
            json.encode({Id = obj.EntityId, Instance = obj.EntityInstance}), false)
        local obj_entity = mdb.get_object(bus, entity_path, "bmc.kepler.Systems.Entity")
        return get_threshold_sensor_rsp(obj, obj_display, obj_entity)
    end
    if discrete_path ~= "" then
        local obj = mdb.get_object(bus, discrete_path, "bmc.kepler.Systems.DiscreteSensor")
        local obj_display = mdb.get_object(bus, discrete_path, "bmc.kepler.Systems.DiscreteSensorDisplay")
        -- 根据EntityId和Instance查找对应的Entity
        local entity_path = bus:call('bmc.kepler.maca', '/bmc/kepler/MdbService', 'bmc.kepler.Mdb', 'GetPath',
            'a{ss}ssb', context.new(), "bmc.kepler.Systems.Entity",
            json.encode({Id = obj.EntityId, Instance = obj.EntityInstance}), false)
        local obj_entity = mdb.get_object(bus, entity_path, "bmc.kepler.Systems.Entity")
        return get_discrete_sensor_rsp(obj, obj_display, obj_entity)
    end
end

return m
